package agents;

import java.io.IOException;
import java.util.ArrayList;

import behaviours.RegisterServiceBehaviour;
import behaviours.SubscribeAndListenBehaviour;
import behaviours.Subscriber;
import model.Artifact;
import model.ContentDTO;
import model.IdentificationProtocol;
import model.Profile;
import model.TourObject;
import gui.ProfilerGUI;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.core.behaviours.TickerBehaviour;
import jade.core.behaviours.WakerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPANames;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;

public class ProfilerAgent extends Agent implements Subscriber {

	private ArrayList<AID> tourguides;
	private ArrayList<AID> curators;
	public Profile profile;

	protected void setup() {
		System.out.println(getAID() + " running setup.");

		tourguides = new ArrayList<AID>();
		curators = new ArrayList<AID>();

		// Create a new profile
		profile = new Profile("Steven", "Bonnel II", true, 23);
		profile.addInterestTag("The Scream");
		profile.addInterestTag("Edvard Munch");
		profile.addInterestTag("Italy");
		profile.addInterestTag("The Statue of David");

		// Discover all services for the GUI
		addBehaviour(new WakerBehaviour(this, 100) {
			@Override
			protected void onWake() {
				addBehaviour(new DiscoverAllServicesBehavior());
			}
		});

		// Subscribe to services to get AID's to curator and tourguide
		String[] providers = { "artifact-provider", "tour-creation" };
		addBehaviour(new SubscribeAndListenBehaviour(this, providers));

		// ------------------------- TASK #1 -------------------------
		// Periodically sends requests with its own profile to the tourguide
		// addBehaviour(new SendTourRequestBehaviour(this, 3000));
		// addBehaviour(new ListenToTourResponsesBehaviour());

		// ------------------------- TASK #2 -------------------------
		// Listen for auction messages
		addBehaviour(new ListenToAuctionBehaviour());

		// Register as an auction participant
		ServiceDescription sd = new ServiceDescription();
		sd.setType("auction-participant");
		sd.setName("auctionprofiler" + Math.round(Math.random() * 1000));
		System.out.println("Registering " + sd.getName()
				+ " as a new auction-participant");
		ServiceDescription[] sds = new ServiceDescription[1];
		sds[0] = sd;
		addBehaviour(new RegisterServiceBehaviour(getAID(), sds));
	}

	protected void takeDown() {
		System.out.println("takeDown()");
	}

	@SuppressWarnings("unused")
	private class SendTourRequestBehaviour extends TickerBehaviour {

		public SendTourRequestBehaviour(Agent a, long period) {
			super(a, period);
		}

		@Override
		protected void onTick() {
			System.out.println("Sending a profile to a tour guide.");

			// Send request to tourguide with this profile
			ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
			for (AID a : tourguides)
				msg.addReceiver(a);
			try {
				msg.setContentObject(new ContentDTO(profile,
						IdentificationProtocol.SENDER_WAS_PROFILER));
			} catch (IOException e) {
				// Shouldn't happen
				e.printStackTrace();
			}
			send(msg);
		}

	}

	private class RequestArtifactBehaviour extends OneShotBehaviour {

		private TourObject tourObject;

		public RequestArtifactBehaviour(TourObject to) {
			tourObject = to;
		}

		@Override
		public void action() {
			System.out.println("Requesting an artifact for curator.");
			// Send request to tourguide with this profile
			ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
			for (AID a : curators) {
				msg.addReceiver(a);
			}
			try {
				msg.setContentObject(new ContentDTO(tourObject,
						IdentificationProtocol.SENDER_WAS_PROFILER));
			} catch (IOException e) {
				// Shouldn't happen
				e.printStackTrace();
			}
			send(msg);
		}
	}

	private class DiscoverAllServicesBehavior extends Behaviour {

		private DFAgentDescription[] providerAgents;

		@Override
		public void action() {
			try {
				providerAgents = DFService.search(myAgent,
						new DFAgentDescription());
				ProfilerGUI gui = new ProfilerGUI(providerAgents);
				gui.setVisible(true);
			} catch (FIPAException e) {
				// Do nothing
			}
		}

		@Override
		public boolean done() {
			return providerAgents != null && providerAgents.length > 0;
		}
	}

	private class ListenToAuctionBehaviour extends CyclicBehaviour {
		private int estimatedObjectValue = 0;

		@Override
		public void action() {
			ACLMessage msg = myAgent
					.receive(MessageTemplate
							.MatchProtocol(FIPANames.InteractionProtocol.FIPA_DUTCH_AUCTION));

			if (msg != null) {

				Object content = null;
				if (msg.hasByteSequenceContent())
					try {
						content = msg.getContentObject();
					} catch (UnreadableException e1) {
						e1.printStackTrace();
					}
				else
					content = msg.getContent();

				switch (msg.getPerformative()) {

				case ACLMessage.INFORM: {
					// Check the content
					if (content instanceof Artifact) {
						// Do an estimation of the artifacts value
						estimatedObjectValue = (int) (13000 + (Math.round(Math
								.random() * 7000)));
						System.out.println(myAgent.getLocalName()
								+ " - estimated value is: "
								+ estimatedObjectValue);
					}
					// If we got here, the inform is for announcing a closed
					// auction
					else if (content instanceof String) {
						System.out
								.println(myAgent.getLocalName()
										+ " - was notified that an auction was closed: "
										+ (String) content);
					} else {
						System.out
								.println("The received content wasn't what we expected (ProfilerAgent).");
					}

					break;
				}
				case ACLMessage.ACCEPT_PROPOSAL: {
					System.out.println(myAgent.getLocalName() + " - won the auction!");
					break;
				}
				case ACLMessage.REJECT_PROPOSAL: {
					System.out.println(myAgent.getLocalName() + " - made a bid but was not the first one to do so :(");
					break;
				}
				case ACLMessage.CFP: {
					if (content instanceof Integer) {
						int offeredPrice = (Integer) content;

						ACLMessage reply = msg.createReply();
						reply.setPerformative(ACLMessage.PROPOSE);

						// If we want to buy it
						if (offeredPrice <= estimatedObjectValue) {
							reply.setContent("ACCEPT");
						}
						// If we don't want to buy it
						else {
							reply.setContent("REFUSE");
						}

						myAgent.send(reply);

					}
					break;
				}

				default:
					break;
				}
			} else {
				block();
			}
		}
	}

	@SuppressWarnings("unused")
	private class ListenToTourResponsesBehaviour extends CyclicBehaviour {

		@Override
		public void action() {
			ACLMessage msg = myAgent.receive(MessageTemplate
					.MatchPerformative(ACLMessage.CONFIRM));

			if (msg != null) {
				try {
					// We are not interested in messages from the DF here.
					if (!msg.getSender().equals(myAgent.getDefaultDF())) {

						Object obj = msg.getContentObject();
						ContentDTO content = null;
						if (obj instanceof ContentDTO)
							content = (ContentDTO) obj;

						if (content != null) {

							// Inform from tourguide
							if (content
									.getSender()
									.equals(IdentificationProtocol.SENDER_WAS_TOUR_GUIDE)) {
								if (content.getContent() instanceof ArrayList<?>) {
									@SuppressWarnings("unchecked")
									ArrayList<TourObject> tourobs = (ArrayList<TourObject>) content
											.getContent();

									for (TourObject to : tourobs) {
										System.out
												.println("Received tour object from tourguide: "
														+ to.getName());
										addBehaviour(new RequestArtifactBehaviour(
												to));
									}
								}
							}
							// Inform from curator
							else if (content.getSender().equals(
									IdentificationProtocol.SENDER_WAS_CURATOR)) {
								if (content.getContent() instanceof Artifact) {
									Artifact art = (Artifact) content
											.getContent();
									System.out.println("Received artifact: "
											+ art.getName()
											+ ", adding to visited.");
									profile.addVisitedArtifact(art.getId(), 3);
								}
							}
						}
					}
				} catch (UnreadableException e) {
					e.printStackTrace();
				}
			} else
				block();
		}
	}

	@Override
	public void updateSubscription(String type, AID name) {
		if (type.equals("tour-creation")) {
			tourguides.add(name);
		} else if (type.equals("artifact-provider")) {
			curators.add(name);
		}
	}
}
